﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using snat.algorithms;
using algorithms.algorithms.SimpleLinkPrediction;
using algorithms.Utils.Metrics;
using snat.model;

namespace algorithms.Algorithms.HierarchicalClustering
{
    public class HierarchicalClusterer : IAlgorithm, ITextRenderable
    {
        private LocalLinkPrediction<double> llp = new LocalLinkPrediction<double>(LinkPredictionMetrics.AdamicAdar);

        public int ClusterCount {
            get;
            private set;
        }


        public HierarchicalClusterer(int _clusterCount) {
            if (_clusterCount < 1) throw new ArgumentException("Must be a positive number of clusters!!");
            ClusterCount = _clusterCount;
        }

        public Dictionary<int, HashSet<ClusterNode>> Clustering { get; private set; }



        private void DoCluster() {
            llp.Process();
            Dictionary<KeyValuePair<BaseNode, BaseNode>, double> scoring = llp.LastScoring;
            int clusters = 0;


            Dictionary<int, List<BaseNode>> clustering = new Dictionary<int,List<BaseNode>>();
            foreach(BaseNode node in scoring.Keys.ToList().ConvertAll(kvp=>kvp.Key)){
                clustering.Add(clusters++, new List<BaseNode>(){node});
            }


            double dist;
            double possibleLinks;
            while (clusters > ClusterCount) {

                double minDist = Double.MaxValue;
                int minC1 = -1;
                int minC2 = -1;
                foreach (int c1 in clustering.Keys) {
                    foreach (int c2 in clustering.Keys) {
                        if (c1 == c2) continue;
                        dist =

                            (from node1 in clustering[c1]
                            select (from node2 in clustering[c2]
                                    select
                                    scoring[new KeyValuePair<BaseNode, BaseNode>(node2, node2)]).Sum()).Sum();




                        possibleLinks = clustering[c1].Count * clustering[c2].Count;
                        dist /= possibleLinks;
                        if (dist < minDist) {
                            minDist = dist;
                            minC1 = c1;
                            minC2 = c2;
                        }
                    }
                }
                
                int mindex = Math.Min(minC1, minC2);
                int maxdex = Math.Max(minC2, minC1);

                clustering[mindex] = clustering[mindex].Union(clustering[maxdex]).ToList();
                clusters--;
            }


            Dictionary<int, HashSet<ClusterNode>> tempClustering = new Dictionary<int, HashSet<ClusterNode>>();
            
            foreach (int clusterID in clustering.Keys) {
                tempClustering.Add(clusterID, new HashSet<ClusterNode>());
                foreach (BaseNode o in clustering[clusterID]) {
                    if (o is ClusterNode) {
                        ClusterNode cn = o as ClusterNode;
                        cn.ClusterID = clusterID;
                        tempClustering[clusterID].Add(cn);
                    }
                }
            }
            Clustering = tempClustering;
        }

        #region IAlgorithm Members

        public string Name {
            get { return "Hierarchical clustering"; }
        }

        public void Register() {
            NetworkModel.Instance.TapeEnded += DoCluster;
        }

        public void DeRegister() {
            NetworkModel.Instance.TapeEnded -= DoCluster;
        }

        #endregion

        #region ITextRenderable Members

        public event RenderTextDelegate RenderText;

        public string GetRenderableText() {
            StringBuilder ret = new StringBuilder();
            IOrderedEnumerable<KeyValuePair<int, HashSet<ClusterNode>>> results = Clustering.ToList().OrderBy(e => e.Key);
            foreach (KeyValuePair<int, HashSet<ClusterNode>> kvp in results) {
                ret.Append("Cluster ");
                ret.Append(kvp.Key.ToString());
                ret.AppendLine(" members:");
                foreach (ClusterNode member in kvp.Value) {
                    ret.AppendLine("\t" + member.Name);
                }
            }




            return ret.ToString();
        }
        #endregion
    }
}
